package com.yunji.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.yunji.common.base.RuoYiConfig;
import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.common.utils.spring.SpringUtils;
import io.swagger.models.auth.In;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

import static com.yunji.common.constant.Constants.HEAD_TOKEN;

@Configuration
@EnableSwagger2    //开启 Swagger2
@EnableKnife4j     //开启 knife4j，可以不写
public class SwaggerConfig {
    private static final Logger logs = LoggerFactory.getLogger(SwaggerConfig.class);

    @Resource
    private RuoYiConfig config;

    @Resource
    private KnifeBean knifeBean;

    // 分割符号
    private static final String DELIMIT = ";";


    /**
     * 是否开启swagger
     */
    @Value("${knife4j.enable:true}")
    private boolean enabled;

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo() {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("标题：咖啡机商城系统_接口文档")
                // 描述
                .description("描述：咖啡机商城系统_接口文档")
                // 作者信息
                .contact(new Contact(config.getName(), null, null))
                // 版本
                .version("版本号:" + config.getVersion())
                .build();
    }

    /**
     * @Title register
     * @Desc 注册文档API容器
     * @Date 2024-05-06 14:58:26.900
     */
    @PostConstruct
    public void register() {
        if (BeanUtils.isNull(knifeBean)) {
            return;
        }
        // 是否启用Swagger
        knifeBean.getList().forEach(bean ->
        {
            String alies = bean.getAlies();

            if (SpringUtils.contains(alies)) {
                return;
            }
            Docket docket = SpringUtils.register(alies, Docket.class, DocumentationType.OAS_30);
            //logs.warn("【文档接口】加载{}配置信息", alies);

            if (BeanUtils.isNull(docket)) {
                return;
            }
            docket.enable(enabled)
                    // 用来创建该API的基本信息，展示在文档的页面中(自定义展示的信息)
                    .apiInfo(apiInfo())
                    // 设置哪些接口暴露给Swagger展示
                    .select()
                    // 扫描所有有注解的api，用这种方式更灵活
                    .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
                    // 扫描指定包中的swagger注解
                    .apis(SwaggerConfig.basePackage(bean.getLocations()))
                    // 扫描所有
//                    .apis(RequestHandlerSelectors.any())
                    .paths(PathSelectors.any())
                    .build()
                    // 分组名称
                    .groupName(bean.getGroup())
                    // 设置安全模式,swagger可以设置访问token
                    .securitySchemes(securitySchemes())
                    // 设置安全上下文
                    .securityContexts(securityContexts());

            logs.trace("【文档接口】完成配置信息{}", docket);
        });
    }

    /**
     * 安全模式，这里指定token通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeyList = new ArrayList<>();
        apiKeyList.add(new ApiKey(HEAD_TOKEN, HEAD_TOKEN, In.HEADER.toValue()));
        return apiKeyList;
    }

    /**
     * 安全上下文
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .operationSelector(o -> o.requestMappingPattern().matches("^(?!auth).*$"))
                        .build());
        return securityContexts;
    }

    /**
     * 默认的安全上引用
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference(HEAD_TOKEN, authorizationScopes));
        return securityReferences;
    }

    /**
     * @return Predicate<RequestHandler>
     * @Title basePackage
     * @Desc 重写basePackage方法，使能够实现多包访问
     * @Date 2024-04-02 14:30:28
     * @Param basePackage 所有包路径
     */
    public static Predicate<RequestHandler> basePackage(final String basePackage) {
        return input -> declaringClass(input).map(handlerPackage(basePackage)::apply).orElse(true);
    }

    /**
     * @return Function<Class < ?>, Boolean>
     * @Title handlerPackage
     * @Desc 重写basePackage方法，使能够实现多包访问
     * @Date 2024-04-02 14:30:48
     * @Param basePackage 所有包路径
     */
    private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
        return input -> {
            // 循环判断匹配
            for (String strPackage : basePackage.split(DELIMIT)) {
                assert input != null;
                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    /**
     * @return Optional<? extends Class < ?>>
     * @Title declaringClass
     * @Desc 重写basePackage方法，使能够实现多包访问
     * @Date 2024-04-02 14:31:15
     * @Param input
     */
    private static Optional<Class<?>> declaringClass(RequestHandler input) {
        return Optional.ofNullable(input.declaringClass());
    }
}
